// XModem协议说明
// XModem 是一种在串口通信中广泛使用的异步文件传输协议
// 分为XModem和1k-XModem协议两种
// 前者使用128字节的数据块
// 后者使用1024字节即1k字节的数据块。
// 本文使用的是1k-XModem协议
// ----------------------------------------------------------------------------------
// |     Byte1     |    Byte2    |     Byte3      | Byte4~Byte131 | Byte132~Byte133 |
// |--------------------------------------------------------------------------------|
// |Start Of Header|Packet Number|~(Packet Number)|  Packet Data  |    16Bit CRC    |
// ----------------------------------------------------------------------------------
// 
// SOH             0x01        Xmodem数据头
// STX             0x02        1K-Xmodem数据头
// EOT             0x04        发送结束
// ACK             0x06        认可响应
// NAK             0x15        不认可响应
// CAN             0x18        撤销传送
// CTRLZ           0x1A        填充数据包
//
// -------------------------------------------------------------------------
// |             SENDER             |          |          RECIEVER         |
// |                                |  <---    |  NAK                      |
// |                                |          |  Time out after 3 second  |
// |                                |  <---    |  NAK                      |
// | SOH|0x01|0xFE|Data[0~127]|CRC| |  --->    |                           |
// |                                |  <---    |  ACK                      |
// | SOH|0x02|0xFD|Data[0~127]|CRC| |  --->    |                           |
// |                                |  <---    |  NAK                      |
// | SOH|0x02|0xFD|Data[0~127]|CRC| |  --->    |                           |
// |                                |  <---    |  ACK                      |
// | SOH|0x03|0xFC|Data[0~127]|CRC| |  --->    |                           |
// |                                |  <---    |  ACK                      |
// | .                              |          |  .                        |
// | .                              |          |  .                        |
// | .                              |          |  .                        |
// |                                |  <---    |  ACK                      |
// | EOT                            |  --->    |                           |
// |                                |  <---    |  ACK                      |
// -------------------------------------------------------------------------


#include <iostream>
#include <fstream>

#include "../Code/SerialControl/SerialControl.h"

bool SYSTEM_RUNNING = true;
SerialControl *serial_handle = nullptr;


#define X_STX 0x02
#define X_ACK 0x06
#define X_NAK 0x15
#define X_EOF 0x04


#define min(a, b)       ((a) < (b) ? (a) : (b))


struct xmodem_chunk {
    uint8_t start;
    uint8_t block;
    uint8_t block_neg;
    uint8_t payload[1024];
    uint16_t crc;
} __attribute__((packed));

#define CRC_POLY 0x1021

static uint16_t crc_update(uint16_t crc_in, int incr)
{
    uint16_t xort = crc_in >> 15;
    uint16_t out = crc_in << 1;

    if (incr)
        out++;

    if (xort)
        out ^= CRC_POLY;

    return out;
}

static uint16_t crc16(const uint8_t *data, uint16_t size)
{
    uint16_t crc, i;

    for (crc = 0; size > 0; size--, data++)
        for (i = 0x80; i; i >>= 1)
            crc = crc_update(crc, *data & i);

    for (i = 0; i < 16; i++)
        crc = crc_update(crc, 0);

    return crc;
}
static uint16_t swap16(uint16_t in)
{
    return (in >> 8) | ((in & 0xff) << 8);
}

typedef enum {
    PROTOCOL_XMODEM,
    PROTOCOL_YMODEM,
}PROTOCOL;

void show_transfer_process_message(int remainder, int total)
{
    int sended = total - remainder;
    printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
    printf("[%d%%](%dK/%dK)", ((sended*100)/total)+1, sended/1024, total/1024);
    fflush(stdout);
}

int xymodem_send(SerialControl *serial_handle , std::string filename, PROTOCOL protocol, int wait)
{

    std::ifstream ubootsplfile(filename, std::ios::in | std::ios::binary);
    if (!ubootsplfile)
    {
        std::cout << "Can't find file";

        return 0;
    }

    ubootsplfile.seekg(0, std::ios_base::end);
    int filesize = static_cast<int>(ubootsplfile.tellg());
    ubootsplfile.seekg(std::ios_base::beg);

    printf("\r\nWaiting for receiver ping ......");
    fflush(stdout);

    std::string rec_buf = "";
    do
    {
        serial_handle->read_data(rec_buf);
    } while (std::string::npos == rec_buf.find("CCC"));

    printf("done.\r\n");
    fflush(stdout);

    printf("\r\nSending .....\r\n");
    fflush(stdout);
    
    uint8_t eof = X_EOF;
    struct xmodem_chunk chunk;
    int skip_payload = 0;

    if (protocol == PROTOCOL_YMODEM) {
        strncpy((char *) chunk.payload, filename.c_str(), sizeof(chunk.payload));
        chunk.block = 0;
        skip_payload = 1;
    } else {
        chunk.block = 1;
    }

    chunk.start = X_STX;

    int len = filesize;
    while (len)
    {
        size_t z = 0;
        int next = 0;

        if (!skip_payload)
        {
            z = min(len, sizeof(chunk.payload));
            ubootsplfile.read((char*)chunk.payload, z);
            memset(chunk.payload + z, 0xff, sizeof(chunk.payload) - z);
        }
        else
        {
            skip_payload = 0;
        }

        chunk.crc = swap16(crc16(chunk.payload, sizeof(chunk.payload)));
        chunk.block_neg = 0xff - chunk.block;

        if(sizeof(chunk) != serial_handle->send_data(&chunk, sizeof(chunk)))
        {
            printf("ERROR\r\n");
        }

        std::string answer;
        do
        {
            serial_handle->read_data(answer);
        } while(answer.empty());

        switch (answer.c_str()[0]) {
        case X_NAK:
            putchar('N');
            break;
        case X_ACK:
            // putchar('.');
            show_transfer_process_message(len, filesize);
            chunk.block++;
            len -= z;
            break;
        default:
            putchar('?');
            break;
        }
        fflush(stdout);
    }
    
    if(sizeof(eof) != serial_handle->send_data(&eof, sizeof(eof)))
    {
        printf("ERROR\r\n");
    }

    /* send EOT again for YMODEM */
    if (protocol == PROTOCOL_YMODEM) {
        if(sizeof(eof) != serial_handle->send_data(&eof, sizeof(eof)))
        {
            printf("ERROR\r\n");
        }
    }

    printf("\r\ndone.\r\n");
    fflush(stdout);

    return 0;
}

int main(void)
{
    std::string filename1 = "/home/dgvy/Workspaces/tiny-serial-terminal/Resource/u-boot-spl.bin";
    std::string filename2 = "/home/dgvy/Workspaces/tiny-serial-terminal/Resource/u-boot.bin";
    PROTOCOL protocol = PROTOCOL::PROTOCOL_XMODEM;

    // 串口对象
    serial_handle = new SerialControl("/dev/ttyUSB0", "115200", &SYSTEM_RUNNING);
    serial_handle->connect();
    serial_handle->set_keyboard_writable(false);
    serial_handle->set_receive_displayable(false);

    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    serial_handle->config(1, 0);

    std::thread r_thread(SerialControl::start_read_thread, serial_handle);
    std::thread w_thread(SerialControl::start_write_thread, serial_handle);

    xymodem_send(serial_handle, filename1, PROTOCOL::PROTOCOL_XMODEM,1);
    xymodem_send(serial_handle, filename2, PROTOCOL::PROTOCOL_YMODEM,1);

    serial_handle->set_keyboard_writable(true);
    serial_handle->set_receive_displayable(true);

    r_thread.join();
    w_thread.join();
}